#!/usr/bin/env python
# -*- coding:utf-8 -*-

import re
import argparse
import subprocess
import os
import sys
import commands
import time
import uuid
import ConfigParser
import random
import shutil

VERSION = "1.0.0"


def exec_cmd(cmd, block=False, cwd=None):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd, shell=True, close_fds=True, executable='bash')
    if block:
        p.wait()
    out = p.stdout
    err = p.stderr
    returncode = p.returncode
    print("{returncode}:{cmd}".format(returncode=returncode, cmd=cmd))
    return out, err, returncode


def perfect_square(val):
    """
    自定义格式
    :param val:
    :return:
    """
    vals = val.split(',')
    pattern = r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
    for value in vals:
        if not re.match(pattern, value):
            print('\033[1;31m The parameter format is incorrect. \033[0m')
            msg = "The parameter format is incorrect."
            raise argparse.ArgumentTypeError(msg)
    return val


def deploy_django(args):
    """
    django项目部署
    :param args:
    :return:
    """
    try:
        is_delete = args.dl
        database_ip = args.d
        redis_ip = args.r
        project_name = args.n
        python_version = args.py
        django_version = args.dj
        xadmin = args.x
        user = args.u
        print('is_delete :\033[1;32m %s \033[0m' % is_delete)
        print('database_ip :\033[1;32m %s \033[0m' % database_ip)
        print('redis_ip :\033[1;32m %s \033[0m' % redis_ip)
        print('project_name :\033[1;32m %s \033[0m' % project_name)
        print('python_version :\033[1;32m %s \033[0m' % python_version)
        print('django_version :\033[1;32m %s \033[0m' % django_version)
        print('xadmin :\033[1;32m %s \033[0m' % xadmin)
        print('user :\033[1;32m %s \033[0m' % user)

        project_path = os.path.join(PROJECT_PATH, project_name)
        print(project_path)

        project_path_inner = os.path.join(project_path, project_name)
        manage_path = os.path.join(project_path, "manage.py")
        apps_path = os.path.join(project_path_inner, "apps")
        init_path = os.path.join(project_path_inner, "__init__.py")
        libs_path = os.path.join(project_path_inner, "libs")
        settings_path = os.path.join(project_path_inner, "settings")
        settings_dev = os.path.join(settings_path, "dev.py")
        utils_path = os.path.join(project_path_inner, "utils")
        main_urls = os.path.join(project_path_inner, "urls.py")


        # # 判断项目文件是否已经存在,存在则退出
        ##if os.path.exists(project_path):
        # #     print '\033[1;32m create project %s false \033[0m' % project_path
        # #     sys.exit()
        # 存在项目，则删掉项目并删除虚拟环境
        if os.path.exists(project_path) and is_delete:
            shutil.rmtree(project_path)
            cmd = "source {} && rmvirtualenv {}".format(VIRTUALENVWRAPPER, project_name)
            exec_cmd(cmd, block=True)

        # 若是想要虚拟环境不重复用,则可以把此处代码放开
        # cmd = "source /home/xsk/.local/bin/virtualenvwrapper.sh " \
        #       "&& workon {}".format(project_name)
        # out, err, returncode = exec_cmd(cmd, block=True)
        # if returncode == 0:
        #     print "已经存在与项目名称{}相同的虚拟环境,请重新确定项目名称".format(project_name)
        #     sys.exit()

        cmds = []
        cmd = "source {} " \
              "&& mkvirtualenv {} -p {} " \
              "&& pip install django=={} -i https://pypi.douban.com/simple " \
              "&& pip install Pillow -i https://pypi.douban.com/simple " \
              "&& pip install PymySQL -i https://pypi.douban.com/simple ".format(VIRTUALENVWRAPPER, project_name, python_version, django_version)

        # 注意：django2和3的版本的djangorestframework包不能超过3.13.1
        if 1.11 >= float(django_version[0:3]) >= 1.8:
            rest_frame_work = "3.8.0"
            cors_headers = "3.0.2"
            django_redis = "5.2.0"
            cmd = cmd + "&& pip install django-redis=={} -i https://pypi.douban.com/simple " \
                        "&& pip install djangorestframework=={} -i https://pypi.douban.com/simple " \
                        "&& pip install django-cors-headers=={} -i https://pypi.douban.com/simple " \
                        "&& pip install djangorestframework-jwt -i https://pypi.douban.com/simple ".format(
                django_redis, rest_frame_work, cors_headers)
        elif 3.2 >= float(django_version[0:3]) >= 2.2:
            rest_frame_work = "3.13.1"
            cors_headers = "3.11.0"
            cmd = cmd + "&& pip install djangorestframework=={} -i https://pypi.douban.com/simple " \
                        "&& pip install django-cors-headers=={} -i https://pypi.douban.com/simple " \
                        "&& pip install djangorestframework-jwt -i https://pypi.douban.com/simple ".format(
                rest_frame_work, cors_headers)
        elif float(django_version[0:3]) > 3.2:
            rest_frame_work = "3.14.0"
            cors_headers = "3.13.0"
            cmd = cmd + "&& pip install djangorestframework=={} -i https://pypi.douban.com/simple " \
                        "&& pip install django-cors-headers=={} -i https://pypi.douban.com/simple " \
                        "&& pip install djangorestframework-jwt -i https://pypi.douban.com/simple ".format(
                rest_frame_work, cors_headers)
        else:
            print("不适用djangorestframework")
            print("不适用django-cors-headers")

        if xadmin and django_version[0] == "2":
            cmd += "&& pip install {}".format(os.path.abspath("xadmin-django2.zip"))

        # 创建项目
        if os.path.exists(project_path):
            sys.exit(1)
        cmd = cmd + "&& django-admin startproject {} ".format(project_name)
        cmds.append(cmd)

        for cmd in cmds:
            # /home/xsk/Desktop
            exec_cmd(cmd, block=True, cwd=PROJECT_PATH)

        # 创建项目后的一些操作
        cmds = []
        cmd = "mkdir logs && touch logs/{}.log".format(project_name)
        cmds.append(cmd)
        cmd = "mkdir scripts && touch scripts/__init__.py"
        cmds.append(cmd)
        cmd = "mkdir {} && touch {}/__init__.py".format(apps_path, apps_path)
        cmds.append(cmd)
        cmd = "mkdir {} && touch {}/__init__.py".format(libs_path, libs_path)
        cmds.append(cmd)
        cmd = "mkdir {} && touch {}/__init__.py".format(settings_path, settings_path)
        cmds.append(cmd)
        cmd = "mkdir {} && touch {}/__init__.py".format(utils_path, utils_path)
        cmds.append(cmd)
        cmd = "cp -ar {}/settings.py {}/dev.py".format(project_path_inner, settings_path)
        cmds.append(cmd)
        cmd = "cp -ar {}/settings.py {}/prod.py".format(project_path_inner, settings_path)
        cmds.append(cmd)
        constant_file = os.path.abspath("constant.py")
        cmd = "cp -ar {} {}".format(constant_file, settings_path)
        cmds.append(cmd)
        cmd = "mv {}/settings.py {}/settings_orgin.py".format(project_path_inner, project_path_inner)
        cmds.append(cmd)
        base_models_file = os.path.abspath("models.py")
        cmd = "cp -ar {} {}".format(base_models_file, utils_path)
        cmds.append(cmd)

        for cmd in cmds:
            exec_cmd(cmd, block=True, cwd=project_path)

        cmds = []
        # 开始修改文件
        # 配置异常处理
        exceptions_file = os.path.abspath("exceptions.py")
        exceptions_dev_file = os.path.abspath("exceptions_dev.py")
        cmd = """sed -ri "s/('EXCEPTION_HANDLER': ')[^.]*/\\1{}/" {}""".format(project_name, exceptions_dev_file)
        cmds.append(cmd)
        cmd = "cp -ar {} {}/utils/".format(exceptions_file, project_path_inner)
        cmds.append(cmd)
        cmd = """cat {} >> {}""".format(exceptions_dev_file, settings_dev)
        cmds.append(cmd)

        # 总路由里面添加一些配置
        cmd = """sed -i "/urlpatterns = /i\\from django.urls import re_path" {}""".format(main_urls)
        cmds.append(cmd)
        cmd = """sed -i "/urlpatterns = /i\\from django.urls import include" {}""".format(main_urls)
        cmds.append(cmd)
        cmd = """sed -i "/urlpatterns = /i\\from django.conf import settings" {}""".format(main_urls)
        cmds.append(cmd)
        cmd = """sed -i "/urlpatterns = /i\\from django.views.static import serve" {}""".format(main_urls)
        cmds.append(cmd)

        # 配置为开发环境
        cmd = """sed -ri "s/(.settings)[^']*/\\1.dev/" {}""".format(manage_path)
        cmds.append(cmd)
        # 插入模块(在第二个三引号下一行插入)
        cmd = '''sed -i "`grep -n '"""' {} | head -2 | tail -1 | cut -d ":" -f 1`a import os\\nimport sys\\nimport configparser\\nfrom {}.settings.constant import CONST" {}'''.format(settings_dev, project_name,settings_dev)
        cmds.append(cmd)
        # 新增导包路径
        cmd = """sed -i '/BASE_DIR = /a\sys.path.insert(0, os.path.join(BASE_DIR,"apps"))' {}""".format(settings_dev)
        cmds.append(cmd)
        # 把app内容插入,例如：corsheaders, rest_framework
        cmd = """sed -i "/INSTALLED_APPS/{n;n;n;n;n;n;s|$|\\n\\n    'corsheaders',\\n    'rest_framework',|}" %s""" % settings_dev
        cmds.append(cmd)
        if xadmin and django_version[0] == "2":
            cmd = """sed -i "/INSTALLED_APPS/{n;n;n;n;n;n;n;n;n;s|$|\\n    'xadmin',|}" %s""" % settings_dev
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /i\import xadmin" {}""".format(main_urls)
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /i\\xadmin.autodiscover()" {}""".format(main_urls)
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /i\\from xadmin.plugins import xversion" {}""".format(main_urls)
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /i\\xversion.register_models()" {}""".format(main_urls)
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /a\    path(r'xadmin/', xadmin.site.urls)," {}""".format(main_urls)
            cmds.append(cmd)
            cmd = """echo "# 设置xadmin用户登录时,登录信息session保存到redis\nSESSION_ENGINE = 'django.contrib.sessions.backends.cache'\nSESSION_CACHE_ALIAS = 'session'\n" """
            cmds.append(cmd)

        # 因为我们用的是JWT所以需要把 CSRF注释掉, 如果用的author认证,则把CSRF打开
        cmd = """sed -i "s/'django.middleware.csrf.CsrfViewMiddleware'/# &/" {}""".format(settings_dev)
        cmds.append(cmd)
        # 把cors加上
        cmd = """sed -i "/MIDDLEWARE/{s|$|\\n    'corsheaders.middleware.CorsMiddleware',|}" %s""" % settings_dev
        cmds.append(cmd)
        # 修改使用中文界面
        cmd = """sed -ri "s/(LANGUAGE_CODE = ')[^']*/\\1zh-Hans/" {}""".format(settings_dev)
        cmds.append(cmd)
        cmd = """sed -ri "s/(TIME_ZONE = ')[^']*/\\1Asia\/Shanghai/" {}""".format(settings_dev)
        cmds.append(cmd)
        cmd = """sed -ri "s/(USE_TZ = )[^ ]*/\\1False/" {}""".format(settings_dev)
        cmds.append(cmd)

        # # 把配置文件放入项目中  弃用
        # config_file = os.path.abspath("config")
        # cmd = "cp -ar {} {}".format(config_file, project_path)
        # cmds.append(cmd)
        # # 把调用ini文件的包命令配上
        # cmd = """sed -i "/DATABASES/i\\cp = configparser.ConfigParser()\\ncp.read('config')" {}""".format(settings_dev)
        # cmds.append(cmd)

        # 在项目主模块的 __init__.py中导入pymysql
        cmd = """echo "import pymysql\npymysql.install_as_MySQLdb()" >> {}""".format(init_path)
        cmds.append(cmd)
        # 修改默认数据库为mysql
        cmd = """sed -ri "s/(django.db.backends)[^']*/\\1.mysql/" {}""".format(settings_dev)
        cmds.append(cmd)
        # # cmd = """sed -i "/ENGINE/a\\        {}" {}""".format("'CONN_MAX_AGE': None", settings_dev)   # 长连接
        # # cmds.append(cmd)
        cmd = """sed -i "/ENGINE/{n;s/.*/        'NAME': CONST.db_name,/;}" %s """ % settings_dev
        cmds.append(cmd)
        cmd = """sed -i "/ENGINE/a\\        {}" {}""".format("'PASSWORD': CONST.db_password,", settings_dev)
        cmds.append(cmd)
        cmd = """sed -i "/ENGINE/a\\        {}" {}""".format("'USER': CONST.db_user,", settings_dev)
        cmds.append(cmd)
        cmd = """sed -i "/ENGINE/a\\        {}" {}""".format("'PORT': CONST.db_port,", settings_dev)
        cmds.append(cmd)
        cmd = """sed -i "/ENGINE/a\\        {}" {}""".format("'HOST': CONST.db_host,", settings_dev)
        cmds.append(cmd)

        # redis
        redis_file = os.path.abspath("redis_code.py")
        cmd = """cat {} >> {}""".format(redis_file, settings_dev)
        cmds.append(cmd)

        # 替换好需要修改的日志路径
        log_file = os.path.abspath("log_demo.py")
        cmd = "sed -ri 's/(logs\/)[^\"]*/\\1{}.log/' {}".format(project_name, log_file)
        cmds.append(cmd)
        # 把日志文件插入到配置文件里面
        cmd = "cat {} >> {}".format(log_file, settings_dev)
        cmds.append(cmd)

        for cmd in cmds:
            exec_cmd(cmd, block=True, cwd=project_path)

        cmds = []
        # 解析python版本信息
        cmd = """echo `source {} && workon {} && python -V`""".format(VIRTUALENVWRAPPER, project_name)
        out, err, returncode = exec_cmd(cmd, block=True)
        python_info = out.read()
        python_info_str = python_info.split(" ")[-1]
        str_index = python_info_str.find(".", 2)
        version_info = "python" + python_info_str[0: str_index]

        # 屏蔽mysql的版本信息
        pymysql_file = "{}/{}/lib/{}/site-packages/django/db/backends/mysql/base.py".format(VIRTUALENVS_PATH, project_name, version_info)
        cmd = """grep -n "if version < (1, 3, 13):" {}""".format(pymysql_file)
        out, err, returncode = exec_cmd(cmd, block=True)
        if not returncode:
            if "if version < (1, 3, 13):" in out.read():
                cmd = """sed -i -e "35 s/^/# /" -i -e "36 s/^/# /" {}""".format(pymysql_file)
                cmds.append(cmd)

        operations_file = "{}/{}/lib/{}/site-packages/django/db/backends/mysql/operations.py".format(VIRTUALENVS_PATH, project_name, version_info)
        cmd = """grep -n "query = query.decode(errors='replace')" {}""".format(operations_file)
        out, err, returncode = exec_cmd(cmd, block=True)
        if not returncode:
            if "query = query.decode(errors='replace')" in out.read():
                # cmd = """sed -i -e "146 s/^/# /" {}""".format(operations_file)
                cmd = """sed -i "s/query = query.decode(errors='replace')/# &/" {}""".format(operations_file)
                cmds.append(cmd)
                cmd = """sed -i "146i \            query = query.encode()" {}""".format(operations_file)
                cmds.append(cmd)

        # 创建自定义的用户模型类
        if user:
            cmd = """source {} && workon {} && cd {} && python ../../manage.py startapp users""".format(VIRTUALENVWRAPPER, project_name, apps_path)
            cmds.append(cmd)
            user_file = os.path.join(apps_path, "users")
            models_file = os.path.abspath("users/models.py")
            user_model_file = os.path.join(user_file, "models.py")
            apps_file = os.path.abspath("users/apps.py")
            user_apps_file = os.path.join(user_file, "apps.py")
            user_init = os.path.join(user_file, "__init__.py")
            views_file = os.path.abspath("users/views.py")
            user_views_file = os.path.join(user_file, "views.py")
            serializers_file = os.path.abspath("users/serializers.py")
            urls_file = os.path.abspath("users/urls.py")
            service_file = os.path.abspath("users/service.py")
            utils_file = os.path.abspath("users/utils.py")
            jwt_auth_file = os.path.abspath("users/jwt_auth.py")

            cmd = "cat {} > {}".format(models_file, user_model_file)
            cmds.append(cmd)
            cmd = """sed -i "/INSTALLED_APPS/{n;n;n;n;n;n;n;n;n;s|$|\\n    'users',|}" %s""" % settings_dev
            cmds.append(cmd)
            cmd = "cat {} > {}".format(apps_file, user_apps_file)
            cmds.append(cmd)
            cmd = """cat {} >> {}""".format(jwt_auth_file, settings_dev)
            cmds.append(cmd)
            cmd = """echo "AUTH_USER_MODEL = 'users.User'" >> {}""".format(settings_dev)
            cmds.append(cmd)

            cmd = """echo "default_app_config = 'users.apps.UsersConfig'" > {}""".format(user_init)
            cmds.append(cmd)
            cmd = """sed -i "/urlpatterns = /a\    path('users/', include('users.urls'))," {}""".format(main_urls)
            cmds.append(cmd)
            cmd = "cat {} > {}".format(views_file, user_views_file)
            cmds.append(cmd)
            cmd = "cp -ar {} {}".format(serializers_file, user_file)
            cmds.append(cmd)
            cmd = "cp -ar {} {}".format(urls_file, user_file)
            cmds.append(cmd)
            cmd = "cp -ar {} {}".format(service_file, user_file)
            cmds.append(cmd)
            cmd = "cp -ar {} {}".format(utils_file, user_file)
            cmds.append(cmd)

        for cmd in cmds:
            exec_cmd(cmd, block=True, cwd=project_path)

        cmd = "source {} && workon {} &&cd {} && python manage.py makemigrations && python manage.py migrate".format(
            VIRTUALENVWRAPPER, project_name, project_path)
        out, err, returncode = exec_cmd(cmd, block=True, cwd=project_path)


        print('\033[1;32m finish \033[0m')

    except Exception as err:
        print('error =\033[1;31m %s \033[0m' % err)


def parser_django(subparsers):
    """
    部署django项目
    :param subparsers:
    :return:
    """
    parser = subparsers.add_parser('django')
    parser.add_argument(
        '-dl',
        default=False,
        type=bool,
        help="is delete project and delete virtual env"
    )
    parser.add_argument(
        '-d',
        default='127.0.0.1',
        type=perfect_square,
        help="Database ip address."
    )
    parser.add_argument(
        '-r',
        default='127.0.0.1',
        type=perfect_square,
        help="Redis ip address."
    )
    parser.add_argument(
        '-n',
        default='demo',
        type=str,
        help="project name"
    )

    parser.add_argument(
        '-py',
        default='python3',
        type=str,
        help="python version required  [python3, python2]",
        required=True,
    )
    parser.add_argument(
        '-dj',
        default='2.2',
        type=str,
        help="django version required [1.1, 2.2, 3.2]",
        required = True,

    )
    parser.add_argument(
        '-x',
        default=False,
        type=bool,
        help="Whether to install xadmin Currently only django2 is supported",
    )
    parser.add_argument(
        '-u',
        default=False,
        type=bool,
        help="Whether to create user model Currently only django is supported",
    )

    parser.set_defaults(func=deploy_django)


def deploy_flask(args):
    """
    flask项目部署
    :param args:
    :return:
    """
    try:
        is_delete = args.dl
        project_name = args.n
        python_version = args.py

        print('is_delete :\033[1;32m %s \033[0m' % is_delete)
        print('project_name :\033[1;32m %s \033[0m' % project_name)
        print('python_version :\033[1;32m %s \033[0m' % python_version)

        project_path = os.path.join(PROJECT_PATH, project_name)
        print(project_path)

        # 存在项目，则删掉项目并删除虚拟环境
        if os.path.exists(project_path) and is_delete:
            shutil.rmtree(project_path)
            cmd = "source {} && rmvirtualenv {}".format(VIRTUALENVWRAPPER, project_name)
            exec_cmd(cmd, block=True)

        cmds = []
        cmd = "source {} " \
              "&& mkvirtualenv {} -p {} " \
              "&& pip install flask==1.1.4 -i https://pypi.douban.com/simple " \
              "&& pip install flask_sqlalchemy==2.5.1 -i https://pypi.douban.com/simple " \
              "&& pip install flask-script==2.0.6 -i https://pypi.douban.com/simple " \
              "&& pip install flask-migrate==2.5.3 -i https://pypi.douban.com/simple " \
              "&& pip install redis==4.3.4 " \
              "&& pip install flask-session==0.4.0 " \
              "&& pip install gevent==21.12.0 " \
              "&& pip install gunicorn".format(VIRTUALENVWRAPPER, project_name, python_version)
        cmds.append(cmd)

        demo_file = os.path.abspath("flask_demo")
        cmd = "cp -ar {} {}".format(demo_file, project_path)
        cmds.append(cmd)

        for cmd in cmds:
            # /home/xsk/Desktop
            exec_cmd(cmd, block=True, cwd=PROJECT_PATH)

        print('\033[1;32m finish \033[0m')
    except Exception as err:
        print('error =\033[1;31m %s \033[0m' % err)


def parser_flask(subparsers):
    """
    # 部署flask项目
    :param subparsers:
    :return:
    """
    parser = subparsers.add_parser('flask')
    parser.add_argument(
        '-dl',
        default=False,
        type=bool,
        help="is delete project and delete virtual env"
    )
    parser.add_argument(
        '-d',
        default='127.0.0.1',
        type=perfect_square,
        help="Database ip address."
    )
    parser.add_argument(
        '-r',
        default='127.0.0.1',
        type=perfect_square,
        help="Redis ip address."
    )
    parser.add_argument(
        '-n',
        default='demo',
        type=str,
        help="project name"
    )

    parser.add_argument(
        '-py',
        default='python3',
        type=str,
        help="python version required  [python3, python2]",
        required=True,
    )

    parser.set_defaults(func=deploy_flask)



def parser_para():
    """
    解析命令行参数
    :return:
    """
    global PROJECT_PATH
    global VIRTUALENVWRAPPER
    global VIRTUALENVS_PATH
    cmd = "hostname"
    out, err, returncode = exec_cmd(cmd, block=True)
    host_name = out.read().strip()
    PROJECT_PATH = "/home/{}/Desktop".format(host_name)
    VIRTUALENVWRAPPER = "/home/{}/.local/bin/virtualenvwrapper.sh".format(host_name)
    VIRTUALENVS_PATH = "/home/{}/.virtualenvs".format(host_name)
    ## "/home/{}/.virtualenvs/demo1/lib/python3.8/site-packages/django/db/backends/mysql/operations.py".format(host_name)
    parser = argparse.ArgumentParser(
        description='python project deployment.'
    )
    parser.add_argument(
        '-v', '--version',
        action='version',
        version='{version}'.format(version=VERSION),
        help='Get the current installed version.'
    )

    subparsers = parser.add_subparsers()

    # 部署django项目
    parser_django(subparsers)

    # 部署flask项目
    parser_flask(subparsers)

    args = parser.parse_args()
    args.func(args)


def main():
    parser_para()


if __name__ == "__main__":
    main()